package com.just.springjpa.specs;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.collect.Iterables.toArray;

/**
 * 定制一个自动模糊查询，对于任意的实体对象进行查询，
 * 有几个值就查几个值，值为字符串类型就执行模糊查询
 * 本次demo重要的知识点：
 * jpa提供了基于准则查询的方式，即Criteria查询。而Spring Data Jpa提供了一个Specification(规范)接口让我们
 * 更方便的构造准则查询，Specification接口定义了一个toPredicate方法来构造查询条件
 * 其中，ROOT来获得需要查询的属性，criteriaBuilder来构造查询条件
 */
public class CustomerSpecs {
    /**
     * 定义一个返回值对象为Specification的方法（Specification:规格）
     * @param entityManager
     * @param example
     * @param <T>
     * @return
     */
    public static <T> Specification<T> byAuto(final EntityManager entityManager,T example){
        //获取当前实体类对象的类型
        final Class<T> type=(Class<T>) example.getClass();
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //新建Predicate列表存储构造的查询条件
                List<Predicate> predicates=new ArrayList<>();
                //获得实体类的EntityType，从EntityType可以获得实体类的属性
                EntityType<T> entityType=entityManager.getMetamodel().entity(type);
                //对实体类的所有属性做循环
                for(Attribute<T,?> attr:entityType.getDeclaredAttributes()){
                    //获得实体类某个对象的值
                    Object attrValue=getValue(example,attr);
                    if(attrValue!=null){
                        //当前属性为字符类型的时候，用模糊查询
                       if(attr.getJavaType()==String.class){
                           if(!StringUtils.isEmpty(attrValue)){
                               //构造当前属性like属性值查询
                               predicates.add(criteriaBuilder.like(root.get(attribute(entityType,attr.getName(),String.class)),
                                       pattern((String)attrValue)));
                           }
                       }else{
                           //其余情况按照构造属性和属性值equal查询
                           predicates.add(criteriaBuilder.equal(root.get(attribute(entityType,attr.getName(),String.class)),
                                   attrValue));
                       }
                    }
                }
                return predicates.isEmpty()?criteriaBuilder.conjunction()
                        :criteriaBuilder.and(toArray(predicates,Predicate.class)); //将条件列表转换成predicate
            }

            //通过反射获得实体类对象对应的属性值
            private <T> Object getValue(T example,Attribute<T,?> attr){
                return ReflectionUtils.getField((Field) attr.getJavaMember(),example);
            }

            //获得实体类的当前属性的SingularAttribute，SingularAttribute包含的是实体类的某个单独属性
            private <E,T>SingularAttribute<T,E> attribute(EntityType<T> entity,String fieldName,Class<E> fieldClass){
                return entity.getDeclaredSingularAttribute(fieldName,fieldClass);
            }
        };
    }

    /**
     * 构造like的查询模式，前后都加%
     * @param str
     * @return
     */
    private static String pattern(String str){
        return "%"+str+"%";
    }


}
